/*
 * Copyright 2018-2021 guerlab.net and other contributors.
 *
 * Licensed under the GNU LESSER GENERAL PUBLIC LICENSE, Version 3 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 *      https://www.apache.org/licenses/LICENSE-2.0
 *
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */
package net.guerlab.smart.pay.service.service.impl;

import net.guerlab.commons.number.NumberHelper;
import net.guerlab.smart.pay.core.domain.PayLogExtends;
import net.guerlab.smart.pay.core.enums.PayStatus;
import net.guerlab.smart.pay.core.exception.*;
import net.guerlab.smart.pay.core.searchparams.PayLogSearchParams;
import net.guerlab.smart.pay.core.searchparams.PayOrderSearchParams;
import net.guerlab.smart.pay.service.entity.PayLog;
import net.guerlab.smart.pay.service.entity.PayOrder;
import net.guerlab.smart.pay.service.mapper.PayLogMapper;
import net.guerlab.smart.pay.service.service.PayLogService;
import net.guerlab.smart.pay.service.service.PayOrderService;
import net.guerlab.smart.pay.stream.binders.PayOrderStatusChangeSenderChannel;
import net.guerlab.smart.platform.server.service.BaseServiceImpl;
import net.guerlab.smart.platform.stream.utils.MessageUtils;
import org.apache.commons.lang3.StringUtils;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.cloud.stream.annotation.EnableBinding;
import org.springframework.stereotype.Service;

import java.time.LocalDateTime;

/**
 * 支付记录服务实现
 *
 * @author guer
 */
@Service
@EnableBinding(PayOrderStatusChangeSenderChannel.class)
public class PayLogServiceImpl extends BaseServiceImpl<PayLog, Long, PayLogMapper, PayLogSearchParams> implements PayLogService {

    private PayOrderService payOrderService;

    private PayOrderStatusChangeSenderChannel statusChangeSender;

    @Override
    public void payed(PayLog entity) {
        PayOrder payOrder = findPayOrder(entity.getPayOrderId());
        LocalDateTime now = LocalDateTime.now();

        boolean needSendNotify = false;

        if (payOrder.getPayStatus() == PayStatus.WAIT_PAY) {
            payOrder.setPayStatus(PayStatus.PAYED);
            payOrder.setPayedTime(now);
            payOrder.setPayedSucceedChannel(entity.getPayChannel());

            payOrderService.updateById(payOrder);
            needSendNotify = true;
        }

        entity.setPayStatus(PayStatus.PAYED);
        entity.setPayedTime(now);

        updateById(entity);

        if (needSendNotify) {
            MessageUtils.send(statusChangeSender.output(), payOrder);
        }
    }

    @Override
    public PayLog create(Long payOrderId, String payChannel, PayLogExtends extend) {
        PayOrder payOrder = findPayOrder(payOrderId);
        return create(payOrder, payChannel, extend);
    }

    @Override
    public PayLog create(String businessGroup, String businessId, String payChannel, PayLogExtends extend) {
        PayOrder payOrder = findPayOrder(businessGroup, businessId);
        return create(payOrder, payChannel, extend);
    }

    private PayLog create(PayOrder payOrder, String payChannel, PayLogExtends extend) {
        String channel = StringUtils.trimToNull(payChannel);

        if (channel == null) {
            throw new PayChannelInvalidException();
        }

        if (payOrder.getPayStatus() != PayStatus.WAIT_PAY) {
            throw new PayStatusErrorException();
        }

        PayLog payLog = new PayLog();
        payLog.setPayLogId(sequence.nextId());
        payLog.setPayOrderId(payOrder.getPayOrderId());
        payLog.setOrderTitle(payOrder.getOrderTitle());
        payLog.setBusinessGroup(payOrder.getBusinessGroup());
        payLog.setBusinessId(payOrder.getBusinessId());
        payLog.setPayChannel(payChannel);
        payLog.setAmount(payOrder.getAmount());
        payLog.setPayStatus(PayStatus.WAIT_PAY);
        payLog.setCreateTime(LocalDateTime.now());
        payLog.setPayTimeoutTime(payOrder.getPayTimeoutTime());
        payLog.setPayedTime(null);
        payLog.setPayCancelTime(null);
        payLog.setExtend(extend == null ? new PayLogExtends() : extend);
        payLog.setExceptionFlag(false);
        payLog.setExceptionReason(null);

        getBaseMapper().insert(payLog);

        return payLog;
    }

    private PayOrder findPayOrder(Long payOrderId) {
        if (!NumberHelper.greaterOrEqualZero(payOrderId)) {
            throw new PayOrderIdInvalidException();
        }

        return payOrderService.selectByIdOptional(payOrderId).orElseThrow(PayOrderInvalidException::new);
    }

    private PayOrder findPayOrder(String businessGroup, String businessId) {
        String tempBusinessGroup = StringUtils.trimToNull(businessGroup);
        String tempBusinessId = StringUtils.trimToNull(businessId);
        if (tempBusinessGroup == null) {
            throw new BusinessGroupInvalidException();
        }
        if (tempBusinessId == null) {
            throw new BusinessIdInvalidException();
        }

        PayOrderSearchParams searchParams = new PayOrderSearchParams();
        searchParams.setBusinessGroup(tempBusinessGroup);
        searchParams.setBusinessId(businessId);

        return payOrderService.selectOneOptional(searchParams).orElseThrow(PayOrderInvalidException::new);
    }

    @Override
    public void markException(Long id, String exceptionReason) {
        exceptionReason = StringUtils.trimToNull(exceptionReason);
        if (NumberHelper.greaterZero(id) && exceptionReason != null) {
            getBaseMapper().markException(id, exceptionReason);
        }
    }

    @Override
    public void removeExceptionMark(Long id) {
        getBaseMapper().removeExceptionMark(id);
    }

    @Autowired
    public void setPayOrderService(PayOrderService payOrderService) {
        this.payOrderService = payOrderService;
    }

    @Autowired
    public void setStatusChangeSender(PayOrderStatusChangeSenderChannel statusChangeSender) {
        this.statusChangeSender = statusChangeSender;
    }
}
